package com.varian.log.util;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.GlobalThreadPool;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.Header;
import cn.hutool.json.JSONUtil;
import com.varian.log.constant.LogConstant;
import com.varian.log.model.ApiLogger;
import com.varian.rabbit.util.RabbitUtil;
import com.varian.tool.Func;
import com.varian.tool.result.IR;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

/**
 * @author ben
 * @since 2024/7/22
 */
@UtilityClass
public class LogUtil {

    private final ThreadLocal<ApiLogger> apiLoggerThreadLocal = new InheritableThreadLocal<>();

    public void apiLog(HttpServletRequest request, String requestLocal, String requestPayload, IR result, Long userId) {
        GlobalThreadPool.execute(() -> saveApiLog(request, requestLocal, requestPayload, result, userId));
    }

    private void saveApiLog(HttpServletRequest request, String requestLocal, String requestPayload, IR result, Long userId) {
        ApiLogger logger = initApiLogger(request, requestLocal, requestPayload, userId);
        if (result != null) {
            logger.setResponsePayload(StrUtil.nullToDefault(JSONUtil.toJsonStr(result), StrUtil.EMPTY))
                    .setHttpStatus(result.getStatus())
                    .setErrorCode(result.getCode());
        }
        RabbitUtil.send(LogConstant.EXCHANGE_LOG, LogConstant.RK_API_LOG, logger);
    }

    private ApiLogger initApiLogger(HttpServletRequest request, String requestLocal, String requestPayload, Long userId) {
        ApiLogger logger = new ApiLogger();
        LocalDateTime requestTime = (LocalDateTime) request.getAttribute(LogConstant.REQUEST_TIME);
        long costTime = LocalDateTimeUtil.between(requestTime, LocalDateTime.now(), ChronoUnit.MILLIS);
        logger.setTraceId(Func.getTraceId(request));
        logger.setServerName(SpringUtil.getApplicationName())
                .setServerHost(request.getLocalAddr())
                .setServerIp(request.getLocalAddr())
                .setCreateTime(requestTime)
                .setCostTime(costTime)
                .setUserAgent(request.getHeader(Header.USER_AGENT.getValue()))
                .setUserIp(request.getRemoteAddr())
                .setRequestMethod(request.getMethod())
                .setRequestUrl(request.getRequestURI())
                .setRequestLocal(requestLocal)
                .setRequestPayload(requestPayload)
                .setCreateId(userId);
        return logger;
    }

    public void apiErrorLog(HttpServletRequest request, Exception exception, IR result) {
        apiErrorLog(request, exception, result, getUserId());
    }

    public void apiErrorLog(HttpServletRequest request, Exception exception, IR result, Long userId) {
        HttpServletRequest clonedRequest = new HttpServletRequestWrapper(request);
        GlobalThreadPool.execute(() -> apiErrorLogAfter(clonedRequest, exception, result, userId));
    }

    public void apiErrorLogPre(HttpServletRequest request, String requestLocal, String requestPayload, Long userId) {
        ApiLogger logger = initApiLogger(request, requestLocal, requestPayload, userId);
        apiLoggerThreadLocal.set(logger);
    }

    public void apiErrorLogAfter(HttpServletRequest request, Exception exception, IR result, Long userId) {
        ApiLogger logger = apiLoggerThreadLocal.get();
        if (logger == null) {
            logger = buildApiErrorLog(request, userId);
        }
        StackTraceElement element = exception.getStackTrace()[0];
        logger.setResponsePayload(JSONUtil.toJsonStr(result))
                .setHttpStatus(result.getStatus())
                .setErrorCode(result.getCode())
                .setExceptionClass(exception.getClass().getName())
                .setExceptionMessage(exception.getMessage())
                .setStackTrace(ExceptionUtil.stacktraceToString(exception))
                .setExceptionLocal(element.getClassName() + "#" + element.getMethodName() + "( " + element.getLineNumber() + "L )");
        RabbitUtil.send(LogConstant.EXCHANGE_LOG, LogConstant.RK_API_ERROR_LOG, logger);
        apiLoggerThreadLocal.remove();
    }

    private ApiLogger buildApiErrorLog(HttpServletRequest request, Long userId) {
        ApiLogger logger = new ApiLogger();
        LocalDateTime requestTime = (LocalDateTime) request.getAttribute(LogConstant.REQUEST_TIME);
        long costTime = LocalDateTimeUtil.between(requestTime, LocalDateTime.now(), ChronoUnit.MILLIS);
        logger.setTraceId(Func.getTraceId(request));
        logger.setServerName(SpringUtil.getApplicationName())
                .setServerHost(request.getLocalAddr())
                .setServerIp(request.getLocalAddr())
                .setCreateTime(requestTime)
                .setCostTime(costTime)
                .setUserAgent(request.getHeader(Header.USER_AGENT.getValue()))
                .setUserIp(request.getRemoteAddr())
                .setRequestMethod(request.getMethod())
                .setRequestUrl(request.getRequestURI())
                .setCreateId(userId)
        ;
        return logger;
    }

    public Long getUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Long userId = 0L;
        try {
            if (authentication != null && authentication.isAuthenticated()) {
                userId = JSONUtil.parseObj(authentication.getPrincipal()).getLong("userId");
            }
        } catch (Exception e) {
            return userId;
        }
        return userId;
    }
}
